package com.autumn.platform.dao.expression;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import com.autumn.platform.core.Utils;
import com.autumn.platform.dao.HandlerRegister;

public class ExpressionParameterHandler implements ParameterHandler {

    private TypeHandlerRegistry typeHandlerRegistry;
    private MappedStatement mappedStatement;
    private Object parameterObject;
    private BoundSql boundSql;
    private Configuration configuration;
    private String databaseId;

    public ExpressionParameterHandler() {}

    public ExpressionParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
        this.databaseId = configuration.getDatabaseId();
    }

    @Override
    public void setParameters(PreparedStatement ps) throws SQLException {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    setParameter(metaObject, parameterMapping, ps, i + 1);
                }
            }
        }
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    protected void setParameter(MetaObject metaObject, ParameterMapping parameterMapping, PreparedStatement ps, int parameterIndex) throws SQLException {
        Object value = evaluateValue(configuration, boundSql, parameterObject, typeHandlerRegistry, metaObject, parameterMapping);
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
        }
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        typeHandler.setParameter(ps, parameterIndex, value, jdbcType);
    }

    public static Object evaluateValue(Configuration configuration, BoundSql boundSql, Object parameterObject,
            TypeHandlerRegistry typeHandlerRegistry, MetaObject metaObject, ParameterMapping parameterMapping) {
        Object value = null;
        String expression = parameterMapping.getExpression();
        if (!Utils.isBlank(expression)) {
            IExpressionHandler handler = HandlerRegister.getHandlerRegistry().getExpressionHandler(expression, configuration);
            if (null != handler) {
                return handler.eval(expression, parameterObject, configuration);
            } else {
                throw new RuntimeException("not found the exception handler of [" + expression + "]");
            }
        } else {
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448  ask first for  additional  params
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
            } else {
                value = metaObject == null ? null : metaObject.getValue(propertyName);
            }
        }
        return value;
    }

    @Override
    public Object getParameterObject() {
        return parameterObject;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public MappedStatement getMappedStatement() {
        return mappedStatement;
    }

    public BoundSql getBoundSql() {
        return boundSql;
    }

    public TypeHandlerRegistry getTypeHandlerRegistry() {
        return typeHandlerRegistry;
    }

    public String getDatabaseId() {
        return databaseId;
    }
}
